package com.ouroboroswiki.core.content.svnkit;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.ISVNFileRevisionHandler;
import org.tmatesoft.svn.core.io.SVNFileRevision;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;

import com.ouroboroswiki.core.AbstractContentRepository;
import com.ouroboroswiki.core.Content;
import com.ouroboroswiki.core.ContentException;
import com.ouroboroswiki.core.ContentPath;
import com.ouroboroswiki.core.ContentUtil;
import com.ouroboroswiki.core.LeafVersion;
import com.ouroboroswiki.core.ListableContentRepository;
import com.ouroboroswiki.core.Version;
import com.ouroboroswiki.core.VersionBuilder;
import com.ouroboroswiki.core.WritableContentRepository;

public class SVNKitContentRepository extends AbstractContentRepository
	implements WritableContentRepository, ListableContentRepository {
	
	private static final Logger log = Logger.getLogger( SVNKitContentRepository.class.getName() );
	
	public static final SVNURL createURL( String url ) throws SVNException {
		if( url.startsWith("file://") ) {
			FSRepositoryFactory.setup();
		} else if( url.startsWith("svn://" ) || url.startsWith("svn+") ) {
			SVNRepositoryFactoryImpl.setup();
		} else if( url.startsWith("http://" ) || url.startsWith("https://") ) {
			DAVRepositoryFactory.setup();
		}
		return SVNURL.parseURIDecoded(url);
	}
	
	public static final SVNRepository createRepository( String url ) throws SVNException {
		return SVNRepositoryFactory.create( createURL(url) );
	}
	
	private SVNDeltaGenerator deltaGenerator;
	//private SVNRepository repository;
	private SVNKitRepositoryFactory repositoryFactory;
	private SVNURL url;
	private String pathPrefix;
	private String pathPostfix;
	private String logMessage;
	private String mimeType;
	
	public SVNKitContentRepository(SVNURL url, SVNKitRepositoryFactory repositoryFactory, String pathPrefix, String pathPostfix, SVNDeltaGenerator deltaGenerator, String logMessage) {
		//this.repository = repository;
		this.url = url;
		this.repositoryFactory = repositoryFactory;
		this.pathPrefix = pathPrefix;
		this.pathPostfix = pathPostfix;
		this.deltaGenerator = deltaGenerator;
		this.logMessage = logMessage;
	}
	
	public String getMimeType() {
		return this.mimeType;
	}
	
	public void setMimeType( String mimeType ) {
		this.mimeType = mimeType;
	}

	public SVNRepository getRepository( Object principal ) throws ContentException {
		SVNRepository repository = this.repositoryFactory.createRepository((ISVNAuthenticationManager)principal, url);
		return repository;
	}


	@Override
	public Version buildVersion(Object principal, ContentPath path,
			String[] versionPath, VersionBuilder versionBuilder)
			throws ContentException {
		String flattenedPath = toPath(path);
		long startRevision = 0;
		// obtain the maximum revision
		MaxFileRevisionHandler maxRevisionHandler = new MaxFileRevisionHandler();
		SVNRepository repository = getRepository( principal );
		try {
			long endRevision = repository.getLatestRevision();			
			repository.getFileRevisions(flattenedPath, startRevision, endRevision, maxRevisionHandler);
		} catch (SVNException ex ) {
			throw new ContentException( "unable to read revisions for "+flattenedPath+" in "+this.getName(), ex );
		}
		return versionBuilder.buildVersion(this.getName(), flattenedPath, versionPath, maxRevisionHandler.maxRevision.getRevision());
	}

	@Override
	public boolean exists(Object principal, ContentPath path) throws ContentException {
		String flattenedPath = toPath(path);
		return exists( principal, flattenedPath );
	}
	
	public boolean exists( Object principal, String flattenedPath ) 
		throws ContentException {
		SVNRepository repository = getRepository( principal );
		return exists( repository, flattenedPath );
	}
	
	public boolean exists( SVNRepository repository, String flattenedPath ) throws ContentException{
		try {
			long endRevision = repository.getLatestRevision();			
			SVNNodeKind nodeKind = repository.checkPath(flattenedPath, endRevision);
			return nodeKind == SVNNodeKind.FILE || nodeKind == SVNNodeKind.DIR;
		} catch( SVNException ex ) {
			throw new ContentException( "unable to check if "+flattenedPath+" in "+this.getName(), ex );
		}
	}

	@Override
	public Content getContent(Object principal, ContentPath path, Version version, Map<String, Object> properties)
			throws ContentException {
		long revisionNumber;
		SVNRepository repository = getRepository( principal );

		LeafVersion leafVersion = ((LeafVersion)version);
		if( leafVersion == null ) {
			try {
				revisionNumber = repository.getLatestRevision();
			} catch( SVNException ex ) {
				throw new ContentException( "unable to read latest revision of repository "+this.getName(), ex );
			}
		} else {
			revisionNumber = leafVersion.getRevisionNumber();
		}
		String name = path.getBaseName();
		String flattenedPath = toPath(name);
		
		SVNKitContent content = new SVNKitContent( repository, revisionNumber, flattenedPath, name, this.getName() );
		content.setMimeType(mimeType);
		content.setWritable(true);
		content.setExists(this.exists(repository, flattenedPath));
		return content;
	}

	@Override
	public void writeToContent(Object principal, String uniqueName, InputStream ins )
			throws IOException, ContentException {
		// check that the directory exists
		String fullName = toPath(uniqueName);
		SVNRepository repository = getRepository( principal );

		try {
			long currentRevision = repository.getLatestRevision();
			if( !exists( repository, fullName ) ) {
				// create the path to the file
				String[] parts = fullName.split("/");
				String dirName = ContentUtil.flatten(parts, 0, parts.length - 1 );
				boolean dirExists = exists( repository, dirName );
				ISVNEditor editor = repository.getCommitEditor(logMessage, null);
				try {
					editor.openRoot(currentRevision);
					// create the directories
					if( !dirExists ) {
						editor.addDir(dirName, null, currentRevision);
					} else if( parts.length > 1 ) {
						editor.openDir(dirName, currentRevision);
					}
					String file = parts[parts.length-1];
					editor.addFile(fullName, null, currentRevision);
					editor.applyTextDelta(file, null);
					String checksum = deltaGenerator.sendDelta( file, ins, editor, true );
					editor.closeFile(file, checksum);
					// close all the directories
					if( parts.length > 1 ) {
						editor.closeDir();
					}
					// close the root
					editor.closeDir();
					editor.closeEdit();
				} catch( Exception ex ) {
					editor.abortEdit();
					throw new ContentException( "unable to create "+fullName, ex );
				}
			} else {
				// read in the file contents for the existing version
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				repository.getFile(fullName, currentRevision, null, bos);
				
				ISVNEditor editor = repository.getCommitEditor(logMessage, null);
				try {
					editor.openRoot(currentRevision);
					editor.openFile(fullName, currentRevision);
					editor.applyTextDelta( fullName, null );
					
					String checksum = deltaGenerator.sendDelta(fullName, new ByteArrayInputStream( bos.toByteArray() ), 0, ins, editor, true );
					
					editor.closeFile(fullName, checksum);
					editor.closeDir();
					editor.closeEdit();
				} catch( Exception ex ) {
					editor.abortEdit();	
					throw new ContentException( "unable to edit "+fullName+" in "+this.getName(), ex );
				}
			}
		} catch( SVNException ex ) {
			throw new ContentException("unable to edit "+fullName+" in "+this.getName(), ex );
		}
			
	}
	
	@Override
	public Collection<String> listContentIds( Object principal ) throws ContentException {
		// TODO assumes path-prefix is a directory (not a file-prefix)
		SVNRepository repository = getRepository( principal );

		try {
			long revision = repository.getLatestRevision();
			ArrayList<String> contentIds = new ArrayList<String>();
			listContentIds( repository, this.pathPrefix, revision, contentIds );
			return contentIds;
		} catch( SVNException ex ) {
			throw new ContentException( "unable to list contents", ex );
		}
	}
	
	@SuppressWarnings("unchecked")
	public void listContentIds( SVNRepository repository, String path, long revision, Collection<String> contentIds ) throws SVNException {

		Collection<SVNDirEntry> entries = repository.getDir(path, revision, null, (Collection)null);
		for( SVNDirEntry entry : entries ) {
			String childPath;
			int pathLength;
			if( this.pathPrefix != null ) {
				pathLength = this.pathPrefix.length();
			} else {
				pathLength = 0;
			}
			if( path.equals( "" ) ) {
				childPath = entry.getName();
			} else {
				childPath = path + '/' + entry.getName();
			}
			if( entry.getKind() == SVNNodeKind.DIR ) {
				listContentIds( repository, childPath, revision, contentIds );
			} else if ( entry.getKind() == SVNNodeKind.FILE ) {
				if( childPath.endsWith(this.pathPostfix) ) {
					String contentId = childPath.substring( 0, childPath.length() - this.pathPostfix.length() );
					contentId = contentId.substring(pathLength);
					if( contentId.startsWith("/")) {
						contentId = contentId.substring(1);
					}
					contentIds.add(contentId);
				}
			} else {
				log.log( Level.WARNING, "unknown node kind returned for "+entry.getName() );
			}
		}
	}

	private String toPath( ContentPath path ) {
		return toPath( path.getBaseName() );
	}
	
	private String toPath( String path ) {
		if( path != null ) {
			return pathPrefix + '/' + path + pathPostfix;
		} else {
			return pathPrefix + pathPostfix;
		}
	}
	
	private class MaxFileRevisionHandler implements ISVNFileRevisionHandler {

		private SVNFileRevision maxRevision = null;
		
		@Override
		public void closeRevision(String arg0) throws SVNException {
			
		}

		@Override
		public void openRevision(SVNFileRevision found) throws SVNException {
			if( maxRevision == null || found.getRevision() > maxRevision.getRevision() ) {
				maxRevision = found;
			}
		}

		@Override
		public void applyTextDelta(String arg0, String arg1)
				throws SVNException {
			
		}

		@Override
		public OutputStream textDeltaChunk(String arg0, SVNDiffWindow arg1)
				throws SVNException {
			return null;
		}

		@Override
		public void textDeltaEnd(String arg0) throws SVNException {
			
		}
	}
	
}
